<!-- 私人聊天室 -->
<template>
  <div>
    <Dialog v-model:visible="renderValues.show"
            :showHeader="false"
            @after-hide="chatHideHandler"
            @show="chatShowHandler"
            contentStyle="overflow: hidden; padding: 0;"
            style="width: 70vw;">
      <div class="grid m-0">
        <!-- 左侧 menu -->
        <div class="col-3 private__chat-list p-0 p-1">
          <div class="private__user-search" style="width: 100%;">
            <div class="p-input-icon-left" style="width: 100%;">
              <i class="pi pi-search"/>
              <InputText v-model="searchContact"
                         class="p-inputtext-ssm"
                         style="width: 100% !important;"
                         type="text" placeholder="搜索用户"/>
            </div>
          </div>
          <!-- 用户卡片 -->
          <div class="private__chat-list-item"
               v-for="(item, index) in showContact"
               :key="`${item.nickname}-${index}`"
               @click="chatToUser(item.nickname, $event)">
            <div class="private__chat-item-avatar">
              <ImgTextAvatar :label="item.nickname"
                             :image="item.avatar"
                             height="40px"
                             width="40px"/>
              <transition appear
                          appear-active-class="animate__animated animate__bounceIn animate__fast"
                          enter-active-class="animate__animated animate__bounceIn animate__fast"
                          leave-active-class="animate__animated animate__bounceOut animate__fast">
                <Badge v-if="item.noticeNum > 0"
                       class="absolute p-badge-no-gutter" style="top:0; left: 0;"
                       :value="item.noticeNum"
                       severity="danger"/>
              </transition>

            </div>
            <div class="private__chat-item-info">
              <div class="private__chat-item-info__nickname">
                {{ item.nickname }}
              </div>
              <div class="private__chat-item-info__last m-text-overflow-ellipsis-2">
                <div class="m-fl">
                  {{ item.lastChat }}
                </div>
                <div class="m-fr">
                  {{ item.lastChatDate }}
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="col-9 p-0">
          <div class="grid m-0" style="height: 100vh;align-content: flex-start;">
            <!-- 头部对话用户的昵称 -->
            <div class="col-12" style="font-size: 20px; font-weight: 500;">
              {{ toUserNickname }}
            </div>
            <!-- 没有选择对象时的样式 -->
            <div v-if="toUserNickname === ''"
                 class="private__chat-container col-12 flex justify-content-center align-items-center"
                 style="height: 85vh;">
              <img :src="emptyChatImg" alt="椰子鸡集团" height="250" width="250" style="filter: grayscale(50%)">
            </div>
            <!-- 整体聊天信息 -->
            <div v-else ref="chatAndEditContainer" class="col-12 grid">
              <div class="col-12 p-0">
                <!-- 聊天容器 -->
                <div class="private__chat-container p-2 pl-3 pb-0" ref="chatContainer">
                  <ChatPanel v-for="(item, index) of chatTranscript"
                             @img-click="imgClickHandler"
                             :key="index"
                             :center="item.center"
                             :right="item.right"
                             :left="item.left"
                             :avatar="item.avatar"
                             :name="item.nickname"
                             :context="item.context">
                  </ChatPanel>
                  <!-- 新消息点击 -->
                  <div v-if="!autoScroll && values.newMsgCount !== 0"
                       class="chat-tip p-2 flex align-content-center justify-content-center align-items-center"
                       @click="goBottom">
                      <span class="flex align-content-center justify-content-center align-items-center pr-3">
                        <svg class="icon pt-1" viewBox="0 0 1024 1024" width="16" height="16"><path
                            d="M493.504 558.144a31.904 31.904 0 0 0 45.28 0l308.352-308.352a31.968 31.968 0 1 0-45.248-45.248L516.16 490.272 221.984 196.128a31.968 31.968 0 1 0-45.248 45.248l316.768 316.768z"
                            fill="#02c402"></path><path
                            d="M801.888 460.576L516.16 746.304 222.016 452.16a31.968 31.968 0 1 0-45.248 45.248l316.768 316.768a31.904 31.904 0 0 0 45.28 0l308.352-308.352a32 32 0 1 0-45.28-45.248z"
                            fill="#02c402"></path></svg>
                      </span>
                    <span
                        class="pr-2"
                        style="border-right: 1px solid rgba(114,104,104,0.36); color: #02c402">
                       {{ values.newMsgCount }}条新的消息
                      </span>
                    <span class="flex align-content-center justify-content-center align-items-center pl-2">
                        <svg viewBox="0 0 1024 1024" width="16" height="16">
                          <path
                              d="M558.669 512l180.312 180.312-46.669 46.67L512 558.668 331.688 738.981l-46.67-46.669L465.332 512 285.019 331.688l46.669-46.67L512 465.332l180.312-180.312 46.67 46.669z">
                        </path>
                        </svg>
                      </span>
                  </div>
                </div>
              </div>
              <!-- 编辑器 -->
              <div class="col-12 pt-3 pb-3">
                <EditTextArea @input-value="editTextInputHandler"
                              @image-value="editTextImageHandler"
                              @on-enter-click="editTextEnterHandler"
                              :update-values="values.inputValue"
                              :update-images="values.imageValue"
                              :height="150"/>
              </div>
              <!-- 发送消息按钮 -->
              <div class="col-12 p-0 text-right">
                <Button label="发送"
                        @click="editTextEnterHandler"
                        :loading="btnLoading"></Button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Dialog>
    <!-- 图片预览 -->
    <div v-show="imageHandler.preview"
         class="p-image-mask p-component-overlay p-component-overlay-enter"
         style="z-index: 99999;">
      <div class="p-image-toolbar">
        <button @click="imageHandler.rotate+=90;" class="p-image-action p-link" type="button"><i
            class="pi pi-refresh"></i></button>
        <button @click="imageHandler.rotate-=90;" class="p-image-action p-link" type="button"><i class="pi pi-undo"></i>
        </button>
        <button @click="imageHandler.scale-=0.1;" class="p-image-action p-link" type="button"><i
            class="pi pi-search-minus"></i></button>
        <button @click="imageHandler.scale+=0.1;" class="p-image-action p-link" type="button"><i
            class="pi pi-search-plus"></i></button>
        <button @click="closePreview" class="p-image-action p-link" type="button"><i
            class="pi pi-times"></i></button>
      </div>
      <div id="image__preview" class="">
        <img :src="imageHandler.src"
             class="p-image-preview p-5"
             :style="`transform: rotate(${imageHandler.rotate}deg) scale(${imageHandler.scale});`">
      </div>
    </div>
  </div>

</template>

<script lang="ts" setup>
/** JS */
import {defineProps, inject, nextTick, reactive, ref, Ref, watch, defineEmits, onMounted, onUpdated} from 'vue';
import Cache from '@/utils/Cache';
import {UserMsg} from '@/interface/user/entity/UserEntityInterface';
import {Websocket} from '@/utils/Webscoket';
import {Message, MessageTypes} from '@/interface/websocket/entity/SocketMessage';
import {
  FileService,
  UserService,
  ChatroomService,
  UserFocusAndFansPaging,
  PaginatorResponseResult
} from '@/utils/ApiRequest';
import toast from '@/utils/Toast';

/** 组件 */
import EditTextArea from '@/components/input/EditTextarea.vue';
import ChatPanel from '@/components/panel/ChatPanel.vue';
import ImgTextAvatar from '@/components/avatar/ImgTextAvatar.vue';
import InputText from 'primevue/inputtext';
import Badge from 'primevue/badge';
import Dialog from 'primevue/dialog';
import Button from 'primevue/button';

/** 用户筛选数据 */
type ChatUserMsg = {
  nickname: string
  avatar: string
  desc: string
}
/** 消息类型 */
type ChatMessage = {
  center?: boolean
  left?: boolean
  avatar?: string
  right: boolean
  nickname: string
  context: string
}
/** 消息通知 */
type ChatMessageNotice = {
  from: ChatUserMsg
  to: string
  text: string
  publishTime?: Date
}
/** 聊天室信息 */
type ChatroomInfo = {
  name: string
  transcript: ChatMessageNotice[]
  userMessages: UserMsg[]
}
const emptyChatImg = require('@/assets/logo.png');
/** 初始化构造对象 */
const cache = new Cache(),
    $ = require('jquery'),
    websocket: Websocket | undefined = inject('websocket'),
    messages: Array<(event) => void> | undefined = inject('messages'),
    userMsg: UserMsg = cache.get('userMsg'),
    fileService = new FileService(),
    userService = new UserService(),
    chatroomService = new ChatroomService(),
    emits = defineEmits(['show', 'hide']);
const focusAndFansPaging: UserFocusAndFansPaging = reactive({
      page: 0,
      limit: 20,
      nickname: ''
    }),
    userContact: Ref<UserMsg[]> = ref([]),
    showContact: Ref<UserMsg[]> = ref([]),
    toUserNickname: Ref<string> = ref('');
/** 获取联系用户 */
const getUserContact = async () => {
      if (userMsg) {
        focusAndFansPaging.nickname = userMsg.nickname;
        let res = await chatroomService.getUserContact(focusAndFansPaging);
        userContact.value = res.data;
        showContact.value = res.data;
        for (let contact of userContact.value) {
          let lastChat = contact.lastChat;
          if (lastChat?.startsWith('<img') && lastChat?.lastIndexOf('/>')) {
            contact.lastChat = '[图片]';
          }
        }
      }
    },
    /** 点击用户进行聊天 */
    chatToUser = async (nickname: string, ev: PointerEvent) => {
      const path = ev.composedPath();
      for (let eventTarget of path) {
        if (eventTarget.classList && (eventTarget as HTMLElement).classList.contains('private__chat-list-item')) {
          const element: HTMLElement = eventTarget as HTMLElement;
          for (let child of element.parentElement.children) {
            if (child.classList.contains('private__chat-list-item-active')) {
              child.classList.remove('private__chat-list-item-active');
            }
          }
          element.classList.add('private__chat-list-item-active');
        }
      }
      let msg: Message = {
        type: MessageTypes.PrivateChat,
        context: {
          from: userMsg,
          to: nickname,
        }
      }
      toUserNickname.value = nickname;
      chatTranscript.value = [];
      for (let contact of userContact.value) {
        if (contact.nickname === toUserNickname.value) {
          // 清除未读数量
          contact.noticeNum = 0;
        }
      }
      websocket?.send(msg)
      nextTick(() => {
        // 监听聊天框
        if (chatContainer.value) {
          let element = (chatContainer.value as HTMLElement);
          // 没有滚动高度 autoScroll 设置为 true
          if (element.scrollHeight == element.clientHeight) {
            autoScroll.value = true;
          }
          document.addEventListener('scroll', () => {
            nextTick(() => {
              if (element) {
                let scrollHeight = element.scrollHeight, scrollTop = element.scrollTop,
                    clientHeight = element.clientHeight;
                let ceilTop = Math.ceil(scrollTop + clientHeight);
                if (ceilTop >= scrollHeight - 100) {
                  // 到底部了
                  values.newMsgCount = 0;
                } else if (element.scrollHeight != element.clientHeight) {
                  // 判断滚动
                  autoScroll.value = (scrollTop >= (scrollHeight - clientHeight) / 2) && values.newMsgCount == 0;
                }
              }
            })
          }, true);
        }
      })
    }

/** 元素钩子 */
const chatContainer = ref(null), chatAndEditContainer = ref(null);
const props = defineProps({
      // 聊天框显隐
      show: {
        type: Boolean,
        default: false
      },
      // 聊天对象
      toChat: {
        type: String,
      }
    }),
    renderValues = reactive({
      show: props.show,
      toChat: props.toChat
    });
watch(props, (n) => {
      if (n.show) {
        getUserContact();
      } else {
        // 用户退出聊天室
        websocket?.send({type: MessageTypes.ExitPrivateChat, context: {from: userMsg}})
      }
      Object.assign(renderValues, n);

    }, {deep: true}
);
/** 动态搜索用户 */
const searchContact = ref('');
watch(searchContact, (n) => {
  if (userContact.value.length > 0) {
    let tempArr = userContact.value;
    let showArr: UserMsg[] = [];
    for (let contact of tempArr) {
      if (contact.nickname.trim().toLocaleLowerCase().indexOf(n.trim().toLocaleLowerCase()) !== -1) {
        showArr.push(contact);
      }
    }
    showContact.value = showArr;
  }
})

const chatHideHandler = () => {
  emits('hide', false);
}, chatShowHandler = () => {
  emits('show', true);
}

// --------- 聊天室 -------------
/** 滑动至聊天框底部 */
function goBottom(behavior: boolean) {
  nextTick(() => {
    if (chatContainer.value) {
      let element = (chatContainer.value as HTMLElement);
      let scrollHeight = element.scrollHeight, clientHeight = element.clientHeight;
      element.scrollTo({top: scrollHeight + clientHeight, behavior: behavior ? 'smooth' : 'auto'})
    }
    if (values.newMsgCount != 0) {
      values.newMsgCount = 0;
    }
  })
}

/** 聊天框数据处理 */
let values = reactive({
  inputValue: '',
  imageValue: [] as any[],
  newMsgCount: 0
})
/** 聊天处理 */
// 新消息自动滚动
const autoScroll = ref(true), btnLoading = ref(false);
// 聊天信息与用户信息
const chatTranscript: Ref<ChatMessage[]> = ref([]), chatUsers: Ref<UserMsg[]> = ref([]);
const sendMsg = (right: boolean, message: { nickname: string, context: string, avatar?: string }) => {
      let context = message.context;
      if (message && (context && context.trim() !== '')) {
        let msg: ChatMessage = {
          center: false,
          right: right,
          left: !right,
          avatar: message.avatar,
          nickname: message.nickname,
          context: message.context
        }
        chatTranscript.value.push(msg);
      }
    },
    sendLeave = (nickname: string) => {
      let msg: ChatMessage = {
        center: true,
        right: false,
        left: false,
        nickname: nickname,
        context: `${nickname} 离开了聊天室`
      }
      chatTranscript.value.push(msg);
    };

const saveImages = async () => {
  let formData = values.imageValue;
  const $edit = $('#yzj__text_area');
  if (formData.length > 0) {
    let fd = new FormData();
    for (let image of formData) {
      fd.append('files', image.get('file'));
    }
    let res = await fileService.uploadChatImage(fd);
    if (res) {
      let images = $edit.find('img');
      for (let i = 0; i < images.length; i++) {
        $(images[i]).attr('src', res[i]);
        $(images[i]).removeAttr('width');
        $(images[i]).css({maxWidth: '200px', borderRadius: '5px', cursor: 'pointer'});
      }
      // 清空 images
      values.imageValue = [];
    }
    return $edit.html()
  }
}
const editTextInputHandler = (val) => {
      values.inputValue = val;
    },
    editTextImageHandler = (val) => {
      // 此处已经获取到了表单
      values.imageValue = val;
    },
    editTextEnterHandler = async () => {
      if (values.inputValue.trim() == '') {
        toast.warn('喂喂喂', '内容不能为空的呀~老糊涂了你', 1500);
      }
      // 发送聊天信息
      btnLoading.value = true;
      let text = await saveImages();
      if (text && text.trim() !== '') {
        values.inputValue = text;
      }
      sendMsg(true, {nickname: userMsg.nickname, context: values.inputValue, avatar: userMsg.avatar});
      let msgNotice: ChatMessageNotice = {
        from: userMsg,
        to: toUserNickname.value,
        text: values.inputValue
      }
      // 查找并且更新最后一条信息
      for (let contact of userContact.value) {
        if (contact.nickname === msgNotice.to) {
          let lastChat = values.inputValue;
          if (lastChat?.startsWith('<img') && lastChat?.lastIndexOf('/>')) {
            lastChat = '[图片]';
          }
          contact.lastChatDate = '当前';
          contact.lastChat = lastChat;
        }
      }
      await websocket?.send({type: MessageTypes.PrivateChat, context: msgNotice});
      btnLoading.value = false;
      values.inputValue = '';
      goBottom(true);
    };

/** 处理通知 */
messages?.push(async (res) => {
  let result: Message = JSON.parse(res.data);
  // 公共聊天室
  if (result.type === MessageTypes.PrivateChat) {
    if (result.context) {
      let notice: any = JSON.parse(result.context) as any;
      // 补充聊天记录
      if (notice instanceof Array) {
        for (let noticeElement of notice) {
          let chatMessageNotice: ChatMessageNotice = noticeElement as ChatMessageNotice
          let isLeft = chatMessageNotice.to !== userMsg.nickname;
          let from = chatMessageNotice.from;
          sendMsg(isLeft, {nickname: from.nickname, avatar: from.avatar, context: chatMessageNotice.text});
        }
        await nextTick(() => {
          goBottom(true);
        })
      }
      // 获取对方发送的消息
      else if (notice.text && notice.from) {
        if (notice.from.nickname === toUserNickname.value) {
          sendMsg(false, {nickname: notice.from.nickname, avatar: notice.from.avatar, context: notice.text});
          values.newMsgCount++;
          // 没有滚动高度 autoScroll 设置为 false
          autoScrollHandler();
        }
        // 查找并且更新最后一条信息
        for (let contact of userContact.value) {
          if (contact.nickname === notice.from.nickname) {
            let lastChat = notice.text;
            if (lastChat?.startsWith('<img') && lastChat?.lastIndexOf('/>')) {
              lastChat = '[图片]';
            }
            if (notice.noticeTime) {
              contact.lastChatDate = notice.noticeTime;
            }
            // 没有激活的时候，进行 ++
            if (contact.nickname !== toUserNickname.value) {
              if (contact.noticeNum === undefined) {
                contact.noticeNum = 1;
              } else {
                contact.noticeNum += 1;
              }
            }
            contact.lastChat = lastChat;
          }
        }
      }
    }
  } else if (result.type === MessageTypes.ExitCommonChat) {
    if (result.context) {
      // 被通知有用户离开聊天室
      values.newMsgCount++;
      let chatUserMsg: ChatUserMsg = JSON.parse(result.context).from as ChatUserMsg;
      let index = 0;
      for (let chatUser of chatUsers.value) {
        if (chatUser.nickname == chatUserMsg.nickname) {
          sendLeave(chatUser.nickname);
          autoScrollHandler();
          break;
        }
        index++;
      }
      chatUsers.value.splice(index, 1);
    }
  }
});
// 点击图片
const imageHandler = reactive({
  preview: false,
  src: '',
  rotate: 0,
  scale: 0.9,
});
const imgClickHandler = async (ev) => {
  imageHandler.preview = true;
  imageHandler.src = ev.src;
}, closePreview = () => {
  imageHandler.preview = false;
  imageHandler.rotate = 0;
  imageHandler.scale = 0.9;
}
document.onkeydown = function (e) {
  if (e.code === 'Escape' && imageHandler.preview) {
    closePreview()
  }
}
/**
 * 监听信息回调
 */
const autoScrollHandler = () => {
  // 没有滚动高度 autoScroll 设置为 false
  if (chatContainer.value && !autoScroll.value) {
    let element = (chatContainer.value as HTMLElement);
    if (element.scrollHeight == element.clientHeight) {
      autoScroll.value = true;
    }
  }
  if (autoScroll.value) {
    goBottom(true);
  }
}

onMounted(() => {

});
</script>

<style lang="less" scoped>
.user-msg {
  &:hover {
    cursor: pointer;
    color: red;
  }
}

.chat-tip {
  &:hover {
    cursor: pointer;
  }

  float: right;
  position: sticky;
  bottom: 0;
  background-color: rgba(255, 255, 255, 0.99);
  color: black;
  padding: 5px;
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
}

.private__chat-container {
  background-color: #f5f5f5;
  overflow-y: scroll;
  height: 55vh;
  position: relative;
}

.p-message-wrapper {
  padding: 0 !important;
}

.private__chat-list {
  .private__user-search {
    padding: 10px;
    text-align: center;
  }

  .private__chat-list-item-active {
    background-color: rgb(200, 198, 198) !important;
  }

  .private__chat-list-item {
    display: flex;
    flex-wrap: wrap;
    transition: .1s all linear;

    &:hover {
      background-color: rgb(200, 198, 198);
    }


    .private__chat-item-avatar {
      position: relative;
      display: flex;
      align-items: center;
      flex: 0 0 auto;
      padding: 0.5rem;
      width: 15%;
    }

    .private__chat-item-info {
      flex: 0 0 auto;
      padding: 0.5rem;
      width: 85%;

      .private__chat-item-info__nickname {
        font-size: 14px;
        color: black;
      }

      .private__chat-item-info__last {
        font-size: 12px;
        color: rgb(153, 153, 153);
      }
    }
  }
}
</style>